var regexLy = /\\[a-z]+|([a-g](?:[ei]s){0,2}|[rs])([,']*)((128|16|1|2|4|8|32|64)(\.{1,2})?(?:\*(\d+)(?:\/(\d+))?)?)?(\\rest)?[[\]()\s~]*/gi;
var lyGroups = {
  'pitch': 1,
  'octave': 2,
  'duration-string': 3,
  'duration': 4,
  'dots': 5,
  'numerator': 6,
  'denominator': 7,
  'rest': 8
};
var voice=[], formatter, ctx, stave, stave2;
$(function(){
  $('#lySop').keyup(function(){
    renderAllVoices();
    //renderVoice($(this).val(),0);
  });
  $('#lyAlt').keyup(function(){
    renderAllVoices();
    //renderVoice($(this).val(),1);
  });
  $('#lyTen').keyup(function(){
    renderAllVoices();
    //renderVoice($(this).val(),2);
  });
  $('#lyBas').keyup(function(){
    renderAllVoices();
    //renderVoice($(this).val(),3);
  });
});

function renderAllVoices() {
  renderVoice($('#lySop').val(),0);
  renderVoice($('#lyAlt').val(),1);
  renderVoice($('#lyTen').val(),2);
  renderVoice($('#lyBas').val(),3);
  renderVoices();
}

function renderVoice(ly,id) {
  if(ctx == null) {
    var canvas = $("#lyPreview")[0];
    var renderer = new Vex.Flow.Renderer(canvas, Vex.Flow.Renderer.Backends.RAPHAEL);
    ctx = renderer.getContext();
    stave = new Vex.Flow.Stave(0, 0, 624);
    stave.addClef("treble").setContext(ctx).draw();
    stave2 = new Vex.Flow.Stave(0,110,624);
    stave2.addClef("bass").setContext(ctx).draw();
  }
  voice[id] = new Vex.Flow.Voice({num_beats:4,beat_value:4,resolution: Vex.Flow.RESOLUTION});
  voice[id].mode = Vex.Flow.Voice.Mode.SOFT;
  for(i=3; i>=0; --i) {
    if(!voice[i]) {
      //var tickables = voice[i].tickables;
      voice[i] = new Vex.Flow.Voice({num_beats:4,beat_value:4,resolution: Vex.Flow.RESOLUTION});
      voice[i].mode = Vex.Flow.Voice.Mode.SOFT;
      //voice[i].addTickables(tickables);
      continue;
    }
  }
  var notes = parseLy(ly,true, id<2? stave : stave2, (id%2==0)? Vex.Flow.StaveNote.STEM_UP : Vex.Flow.StaveNote.STEM_DOWN);
  voice[id].addTickables(notes.notes);
  //renderVoices();
}

function renderVoices() {
  ctx.clear();
  stave.draw();
  stave2.draw();
  var formatter = new Vex.Flow.Formatter().joinVoices(voice).format(voice,624).format(voice,624);
  //formatter.joinVoices(voice.slice(1,2)).format(voice.slice(1,2),624);
  //formatter.joinVoices(voice.slice(2,3)).format(voice.slice(2,3),624);
  //formatter.joinVoices(voice.slice(2)).format(voice.slice(2),624);
  voice[0].draw(ctx,stave);
  voice[1].draw(ctx,stave);
  voice[2].draw(ctx,stave2);
  voice[3].draw(ctx,stave2);
}

function parseLy(ly,relativePitch,stave,stemDir){
  var result = {notes:[],ticks:0},
      match,
      noteInfo,
      note,
      duration = "4",
      octave = 4,
      pitchName,
      pitch,
      accidentals,
      lastPitch = 0; // C
  var totalTicks = 0;
  regexLy.exec(null);
  while(match = regexLy.exec(ly)) {
    var multiplier = 1;
    if(match[lyGroups.pitch]) {
      accidentals = {};
      duration = match[lyGroups.duration] || duration;
      cDuration = duration;
      for(i = (match[lyGroups.dots]||'').length; i>0; --i) {
        cDuration += 'd';
        multiplier *= 1.5;
      }
      pitchName = match[lyGroups.pitch].replace(/^es$/,'ees').replace(/is/g,'#').replace(/es/g,'b');
      if(pitchName[0] == 's') {
        noteInfo = { clef: 'treble', keys: ["b/4"], duration: cDuration + 'r'};
        note = new Vex.Flow.StaveNote(noteInfo);
      } else if(pitchName[0] == 'r') {
        noteInfo = { clef: 'treble', keys: ["b/4"], duration: cDuration + 'r'};
        note = new Vex.Flow.StaveNote(noteInfo);
      } else {
        if(pitchName.length > 1) {
          accidentals[0] = new Vex.Flow.Accidental(pitchName.slice(1));
        }
        pitch = (parseInt(pitchName[0],17) - 5) % 7;
        if(relativePitch) {
          if(match[lyGroups.octave]) {
            octave += (match[lyGroups.octave].match(/'/g)||[]).length;
            octave -= (match[lyGroups.octave].match(/,/g)||[]).length;
          }
          var relPitch = pitch - lastPitch;
          if(relPitch > 3) {
            // new pitch should be below last pitch
            relPitch -= 7;
          } else if(relPitch < -3) {
            // new pitch should be above last pitch
            relPitch += 7;
          } // else new pitch should be above/below last pitch if relPitch is positive/negative.
          var newPitch = lastPitch + relPitch;
          newPitch = Math.floor(newPitch/7);
          lastPitch = Math.floor(lastPitch/7);
          octave += newPitch - lastPitch;
        } else {
          octave = match[lyGroups.octave].length;
        }
        pitchName += '/' + octave;
        noteInfo = { clef: stave.clef || 'treble', keys: [pitchName], duration: cDuration};
        if(match[lyGroups.rest]) {
          noteInfo.duration += 'r';
        }
        note = new Vex.Flow.StaveNote(noteInfo);
      }
      if(typeof(stemDir)!='undefined') note.setStemDirection(stemDir);
      for(i in accidentals) {
        //TODO: decide if the accidental needs to be shown...
        note.addAccidental(parseInt(i), accidentals[i]);
      }
      for(i = (match[lyGroups.dots]||'').length; i>0; --i) {
        note.addDotToAll();
      }
      //note.setStave(stave);
      result.notes.push(note);
      
      lastPitch = pitch;
      totalTicks += multiplier * (4/duration);
    }
  }
  result.ticks = totalTicks;
  return result;
}